#include <stdio.h>
#include <stdlib.h>

#define _USE_MATH_DEFINES
#include <math.h>

#if defined(USE_LD)
#if defined(WIN32)
#include "windows.h"
#include "GL/glew.h"
#include "SDL.h"
#elif defined(__APPLE__)
#include <math.h>
#include "GL/glew.h"
#include "SDL/SDL.h"
#else
#include <math.h>
#include "GL/glew.h"
#include "SDL.h"
#endif
#else
#include <math.h>
#include "GL/gl.h"
#include "GL/glext.h"
#include "GL/glu.h"
#include "SDL.h"
#endif

#include <float.h>

#if defined(USE_LD)
#define SFUNCTION static
#define SCONST static const
#else
#define SFUNCTION static
#define SCONST static
#endif

//######################################
// Loader ##############################
//######################################

#if !defined(WIN32)
#include "dlfcn.h"
#endif

/** Dynamically loaded symbols in one segment. */
static char dnload_symbols[] =
"libGL.so\0"
"glAttachShader\0"
"glBlendFunc\0"
"glClear\0"
"glCompileShader\0"
"glCreateProgram\0"
"glCreateShader\0"
"glDrawElements\0"
"glEnable\0"
"glInterleavedArrays\0"
"glLinkProgram\0"
"glLoadIdentity\0"
"glMatrixMode\0"
"glShaderSource\0"
"glUniform4fv\0"
"glUseProgram\0\0"
"libGLU.so\0"
"gluLookAt\0"
"gluPerspective\0\0"
"libSDL.so\0"
"SDL_GL_SwapBuffers\0"
"SDL_Init\0"
"SDL_PollEvent\0"
"SDL_Quit\0"
"SDL_SetVideoMode\0\0";

#define dcosf cosf
#define dsinf sinf
#define dsqrtf sqrtf

#define dnload_pointer(offset) (*((void**)(dnload_symbols + offset)))

#if defined(USE_LD)

#define dlAttachShader glAttachShader
#define dlBlendFunc glBlendFunc
#define dlClear glClear
#define dlCompileShader glCompileShader
#define dlCreateProgram glCreateProgram
#define dlCreateShader glCreateShader
#define dlDrawElements glDrawElements
#define dlEnable glEnable
#define dlInterleavedArrays glInterleavedArrays
#define dlLinkProgram glLinkProgram
#define dlLoadIdentity glLoadIdentity
#define dlMatrixMode glMatrixMode
#define dlShaderSource glShaderSource
#define dlUniform4fv glUniform4fv
#define dlUseProgram glUseProgram

#define dluLookAt gluLookAt
#define dluPerspective gluPerspective

#define DDL_GL_SwapBuffers SDL_GL_SwapBuffers
#define DDL_Init SDL_Init
#define DDL_PollEvent SDL_PollEvent
#define DDL_SetVideoMode SDL_SetVideoMode
#define DDL_Quit SDL_Quit

#else

#define dlAttachShader ((void (*)(GLuint, GLuint))(dnload_pointer(0)))
#define dlBlendFunc ((void (*)(GLenum, GLenum))(dnload_pointer(9)))
#define dlClear ((void (*)(GLbitfield))(dnload_pointer(24)))
#define dlCompileShader ((void (*)(GLuint))(dnload_pointer(36)))
#define dlCreateProgram ((GLuint (*)(void))(dnload_pointer(44)))
#define dlCreateShader ((GLuint (*)(GLenum))(dnload_pointer(60)))
#define dlDrawElements ((void (*)(GLenum, GLsizei, GLenum, const GLvoid*))(dnload_pointer(76)))
#define dlEnable ((void (*)(GLenum))(dnload_pointer(91)))
#define dlInterleavedArrays ((void (*)(GLenum, GLsizei, const GLvoid*))(dnload_pointer(106)))
#define dlLinkProgram ((void (*)(GLuint))(dnload_pointer(115)))
#define dlLoadIdentity ((void (*)(void))(dnload_pointer(135)))
#define dlMatrixMode ((void (*)(GLenum))(dnload_pointer(149)))
#define dlShaderSource ((void (*)(GLuint, GLsizei, const GLchar**, const GLint*))(dnload_pointer(164)))
#define dlUniform4fv ((void (*)(GLint, GLsizei, const GLfloat*))(dnload_pointer(177)))
#define dlUseProgram ((void (*)(GLuint))(dnload_pointer(192)))

#define dluLookAt ((void (*)(GLdouble, GLdouble, GLdouble, GLdouble, GLdouble, GLdouble, GLdouble, GLdouble, GLdouble))(dnload_pointer(219)))
#define dluPerspective ((void (*)(GLdouble, GLdouble, GLdouble, GLdouble))(dnload_pointer(229)))

#define DDL_GL_SwapBuffers ((void (*)())(dnload_pointer(255)))
#define DDL_Init ((int (*)(Uint32))(dnload_pointer(265)))
#define DDL_PollEvent ((int (*)(SDL_Event*))(dnload_pointer(284)))
#define DDL_Quit ((void (*)())(dnload_pointer(293)))
#define DDL_SetVideoMode ((SDL_Surface* (*)(int, int, int, Uint32))(dnload_pointer(307)))

#endif

//######################################
// Define ##############################
//######################################

#define SWAN_RADIUS 50.0f
#define TRIANGLE_SIZE 20.0f
#define INITIAL_R 24.0f
#define INITIAL_H 20.0f
#define SWAN_LAYER_ANGLE_STEP 8.0f

#define CAGE_RADIUS SWAN_RADIUS*2.5f
#define CAGE_DETAIL 200
#define CAGE_HEIGHT SWAN_RADIUS*1.5f
#define CAGE_BAR_THICKNESS 1.0f

#define TABLE_DETAIL 200
#define TABLE_HEIGHT 300.0f
#define TABLE_FOOT_RADIUS 40.0f

#define TABLE_RADIUS 200.0f
#define TABLE_SIDE_RADIUS 10.0f
#define TABLE_FLAT_PART_LEN 40.0f

#define NEWTON_DETAIL 1000

#define DRAW_HEAP 8*(NEWTON_DETAIL * NEWTON_DETAIL)

//######################################
// Types ###############################
//######################################

typedef struct {
  float x,y,z;
} point;

typedef struct iarray_struct
{
  float cr;
  float cg;
  float cb;
  float ca;
  float nx;
  float ny;
  float nz;
  float px;
  float py;
  float pz;
} interleaved_array;

static interleaved_array draw_array[DRAW_HEAP];
static GLuint index_array[DRAW_HEAP * 4];

static interleaved_array *diter = draw_array; 
static GLuint iiter = 0;

//######################################
// OpenGL draw #########################
//######################################

/* Cross Product */
SFUNCTION void CrossProduct(point *i1, point *i2, point *dst)
{
  dst->x = i1->y * i2->z - i1->z * i2->y;
  dst->y = i1->z * i2->x - i1->x * i2->z;
  dst->z = i1->x * i2->y - i1->y * i2->x;

  {
    float len = (float)dsqrtf(dst->x * dst->x + dst->y * dst->y + dst->z * dst->z);

    dst->x /= len;
    dst->y /= len;
    dst->z /= len;
  }
}

SFUNCTION void Draw_One_Triangle(point *p1, point *p2, point *p3)
{
  point p4, p5;

  diter[0].px = p1->x;
  diter[0].py = p1->y;
  diter[0].pz = p1->z;

  diter[1].px = p2->x;
  diter[1].py = p2->y;
  diter[1].pz = p2->z;

  diter[2].px = p3->x;
  diter[2].py = p3->y;
  diter[2].pz = p3->z;

  p4.x = p2->x - p1->x;
  p4.y = p2->y - p1->y;
  p4.z = p2->z - p1->z;
  
  p5.x = p3->x - p1->x;
  p5.y = p3->y - p1->y;
  p5.z = p3->z - p1->z;

  CrossProduct(&p4, &p5, (point*)(&(diter[0].nx)));
  diter[2].nx = diter[1].nx = diter[0].nx;
  diter[2].ny = diter[1].ny = diter[0].ny;
  diter[2].nz = diter[1].nz = diter[0].nz;

#if 0
  diter[0].nx = p4.y * p5.z - p4.z * p5.y;
  diter[0].ny = p4.z * p5.x - p4.x * p5.z;
  diter[0].nz = p4.x * p5.y - p4.y * p5.x;

  diter[2].nx = diter[1].nx = diter[0].nx;
  diter[2].ny = diter[1].ny = diter[0].ny;
  diter[2].nz = diter[1].nz = diter[0].nz;
#endif

  diter[0].cr = diter[1].cr = diter[2].cr = 1.0f;
  diter[0].cg = diter[1].cg = diter[2].cg = 1.0f;
  diter[0].cb = diter[1].cb = diter[2].cb = 1.0f;
  diter[0].ca = diter[1].ca = diter[2].ca = 1.0f;

  index_array[iiter] = iiter++;
  index_array[iiter] = iiter++;
  index_array[iiter] = iiter++;
  diter += 3;
}

/* Let's draw a paper triangle */
SFUNCTION void Draw_Paper_Triangle(point *head, point *base, point *flapL, point *flapR)
{
  Draw_One_Triangle(head, base, flapL);

  Draw_One_Triangle(head, flapR, base);

  Draw_One_Triangle(head, flapL, flapR);

  Draw_One_Triangle(base, flapR, flapL);
}

SFUNCTION void Draw_Flap_Circle(float r_base, float h_base, float r_head,
    float h_head, float r_flaps, float h_flaps, int flaps, float begin,
    float end)
{
  point base, head, flapL, flapR;
  float angle_unit;
  int i;
  begin = (float)(begin*M_PI/120.0);
  end = (float)(end*M_PI/120.0);
  //begin = (float)(begin*M_PI/180.0);
  //end = (float)(end*M_PI/180.0);

  // Katsotaanpa kuinka auki kukin paperikolmio on
  angle_unit = (end-begin) / (2*(float)flaps);

  for(i=0; i<flaps; i++)
  {
    base.x = r_base*(float)dcosf(begin+(i*2+1)*angle_unit);
    base.y = h_base;
    base.z = r_base*(float)dsinf(begin+(i*2+1)*angle_unit);
    head.x = r_head*(float)dcosf(begin+(i*2+1)*angle_unit);
    head.y = h_head;
    head.z = r_head*(float)dsinf(begin+(i*2+1)*angle_unit);
    flapL.x = r_flaps*(float)dcosf(begin+i*2*angle_unit);
    flapL.y = h_flaps;
    flapL.z = r_flaps*(float)dsinf(begin+i*2*angle_unit);
    flapR.x = r_flaps*(float)dcosf(begin+(i+1)*2*angle_unit);
    flapR.y = h_flaps;
    flapR.z = r_flaps*(float)dsinf(begin+(i+1)*2*angle_unit);
    Draw_Paper_Triangle(&head, &base, &flapL, &flapR);
  }
}

SCONST unsigned char neck_position_data[] =
{
   8, 0,
  17, 5,
  26, 11,
  32, 18,
  36, 27,
  39, 35,
  40, 46,
  39, 54,
  37, 64,
  34, 73,
  29, 82,
  24, 89,
  18, 98,
  12, 105,
   6, 113,
   2, 122,
   0, 132,
   1, 142,
   4, 150,
  10, 156,
  17, 160,
  25, 162,
  35, 161,
  45, 157
};

/*static const unsigned short int neck_angle_data[] =
{
  
   341,
     0,
     9,
    25,
    48,
    47,
    57,
    67,
    75,
    84,
    93,
    95,
    96,
    94,
    86,
    75,
    58,
    41,
    28,
    11,
   348,
   334,
   311,
   293
};*/

SCONST unsigned char neck_angle_data[] =
{
   227,
     0,
     6,
    17,
    32,
    35,
    39,
    45,
    50,
    56,
    62,
    63,
    64,
    63,
    57,
    50,
    39,
    27,
    19,
     7,
   232,
   223,
   207,
   195
};

SFUNCTION void Draw_Neck(float x, float y, float z)
{
  point base, head, flapL, flapR;
  double angle;
  int i;
  for(i=0; i<24; i++)
  {
    //angle = (float)(neck_angle_data[i]*M_PI/180.0);
    angle = (float)(neck_angle_data[i]*M_PI/120.0f);
    base.x = x + neck_position_data[i*2+0];
    base.y = y + neck_position_data[i*2+1];
    base.z = z;
    head.x = base.x + TRIANGLE_SIZE*(float)dsinf(angle);
    head.y = base.y - TRIANGLE_SIZE*(float)dcosf(angle);
    head.z = base.z;
    
    flapL.x = base.x + TRIANGLE_SIZE*(float)dcosf(angle);
    flapL.y = base.y + TRIANGLE_SIZE*(float)dsinf(angle);
    flapL.z = base.z - (0.4f - i/80.0f)*TRIANGLE_SIZE;
    
    flapR.x = flapL.x;
    flapR.y = flapL.y;
    flapR.z = base.z + (0.4f - i/80.0f)*TRIANGLE_SIZE;
    Draw_Paper_Triangle(&head, &base, &flapL, &flapR);
  }
}

/** Cylinder detail leve. */
#define CYLINDER_DETAIL 100

/** Cylinder step variable. */
#define CYLINDER_STEP (float)(2.0f*M_PI/(float)CYLINDER_DETAIL)

/* Draw a cylinder */
SFUNCTION void Draw_Cylinder(point *begin, point *end, float r1, float r2)
{
  // Calculate two normals for the vector
  point n1,n2,temp1,temp2,temp3,temp4;
  int i;
  temp1.x = end->x - begin->x;
  temp1.y = end->y - begin->y;
  temp1.z = end->z - begin->z;
  temp2.x = temp2.y = temp2.z = 128.0f;
  
  CrossProduct(&temp1, &temp2, &n1);
  CrossProduct(&temp1, &n1, &n2);

  for(i = 0; (i < CYLINDER_DETAIL); i++)
  {
    float  tmpi = i * CYLINDER_STEP,
          ci = dcosf(tmpi),
          si = dsinf(tmpi),
          ci1 = dcosf(tmpi + CYLINDER_STEP),
          si1 = dsinf(tmpi + CYLINDER_STEP);          
    temp1.x = begin->x + r1 * (n1.x * ci + n2.x * si);
    temp1.y = begin->y + r1 * (n1.y * ci + n2.y * si);
    temp1.z = begin->z + r1 * (n1.z * ci + n2.z * si);
    temp2.x = begin->x + r1 * (n1.x * ci1 + n2.x * si1);
    temp2.y = begin->y + r1 * (n1.y * ci1 + n2.y * si1);
    temp2.z = begin->z + r1 * (n1.z * ci1 + n2.z * si1);
    temp3.x = end->x + r2 * (n1.x * ci + n2.x * si);
    temp3.y = end->y + r2 * (n1.y * ci + n2.y * si);
    temp3.z = end->z + r2 * (n1.z * ci + n2.z * si);
    temp4.x = end->x + r2 * (n1.x * ci1 + n2.x * si1);
    temp4.y = end->y + r2 * (n1.y * ci1 + n2.y * si1);
    temp4.z = end->z + r2 * (n1.z * ci1 + n2.z * si1);

    //Muista tsekata tämä -> kaksi funktiokutsua veke
    Draw_One_Triangle(&temp1,&temp2,&temp3);
    Draw_One_Triangle(&temp2,&temp1,&temp3);

    Draw_One_Triangle(&temp2,&temp3,&temp4);
    Draw_One_Triangle(&temp2,&temp4,&temp3);
  }
}

#define BUFFER_SIZE 1024

//static float buffer_variable[BUFFER_SIZE];

/* Draw the Cage */
/*static void Draw_Cage(int sectionsx, int sectionsy)
{
  point temp[BUFFER_SIZE];
  float temporary1, temporary2;
  int i,j;
  //Pillars up
  for(j=0; j<=sectionsy; j++)
  {
    for(i=0; i<=sectionsx; i++)
    {
      temporary1 = (float)(i*2*M_PI/(float)sectionsx);
      temp[j*sectionsx+i].x = CAGE_RADIUS*(float)dcosf(temporary1);
      temp[j*sectionsx+i].z = CAGE_RADIUS*(float)dsinf(temporary1);
      temp[j*sectionsx+i].y = j*CAGE_HEIGHT;
      if(i>0)
      {
        Draw_Cylinder(&temp[j*sectionsx+i],&temp[j*sectionsx+i-1],CAGE_BAR_THICKNESS,CAGE_BAR_THICKNESS);
      }
      if(j>0)
      {
        Draw_Cylinder(&temp[(j-1)*sectionsx+i],&temp[j*sectionsx+i],CAGE_BAR_THICKNESS,CAGE_BAR_THICKNESS);
      }
      else
      {
        temp[i+1].x = temp[i+1].y = temp[i+1].z = 0.0;
        Draw_Cylinder(&temp[i],&temp[i+1],CAGE_BAR_THICKNESS,CAGE_BAR_THICKNESS);
      }
    }
  }
  //Round roof
  for(j=0; j<=CAGE_DETAIL; j++)
  {
    for(i=0; i<sectionsx; i++)
    {
      temporary1 = (float)(i*2*M_PI/(float)sectionsx);
      temporary2 = (float)(j*(M_PI/2.0)/CAGE_DETAIL);
      temp[j*sectionsx+i].x = CAGE_RADIUS*(float)(dcosf(temporary2)*dcosf(temporary1));
      temp[j*sectionsx+i].z = CAGE_RADIUS*(float)(dcosf(temporary2)*dsinf(temporary1));
      temp[j*sectionsx+i].y = (sectionsy+(float)dsinf(temporary2))*CAGE_HEIGHT;
      if(j>0)
      {
        Draw_Cylinder(&temp[(j-1)*sectionsx+i],&temp[j*sectionsx+i],CAGE_BAR_THICKNESS,CAGE_BAR_THICKNESS);
      }
    }
  }
}*/

/* Draw the Cage */
SFUNCTION void Draw_Cage(int sectionsx, int sectionsy)
{
  point temp1,temp2;
  int i,j;
  //Pillars up
  for(j=0; j<sectionsy; j++)
  {
    for(i=0; i<sectionsx; i++)
    {
      temp1.x = CAGE_RADIUS*(float)dcosf(i*2*M_PI/(float)sectionsx);
      temp1.z = CAGE_RADIUS*(float)dsinf(i*2*M_PI/(float)sectionsx);
      temp1.y = j*CAGE_HEIGHT;
      temp2.x = temp2.y = temp2.z = 0.0;
      if(j==0)
      {
        Draw_Cylinder(&temp1,&temp2,CAGE_BAR_THICKNESS,CAGE_BAR_THICKNESS);
      }
      temp2.x = temp1.x;
      temp2.z = temp1.z;
      temp2.y = (j+1)*CAGE_HEIGHT;
      Draw_Cylinder(&temp1,&temp2,CAGE_BAR_THICKNESS,CAGE_BAR_THICKNESS);
      temp1.x = CAGE_RADIUS*(float)dcosf((i+1)*2*M_PI/(float)sectionsx);
      temp1.z = CAGE_RADIUS*(float)dsinf((i+1)*2*M_PI/(float)sectionsx);
      temp1.y = (j+1)*CAGE_HEIGHT;
      Draw_Cylinder(&temp1,&temp2,CAGE_BAR_THICKNESS,CAGE_BAR_THICKNESS);
      if(j==0)
      {
        temp1.y=temp2.y=0;
        Draw_Cylinder(&temp1,&temp2,CAGE_BAR_THICKNESS,CAGE_BAR_THICKNESS);
      }
    }
  }
  //Round roof
  for(j=0; j<CAGE_DETAIL; j++)
  {
    for(i=0; i<sectionsx; i++)
    {
      temp1.x = (float)dcosf(j*(M_PI/2.0)/CAGE_DETAIL)*CAGE_RADIUS*(float)dcosf(i*2*M_PI/(float)sectionsx);
      temp1.z = (float)dcosf(j*(M_PI/2.0)/CAGE_DETAIL)*CAGE_RADIUS*(float)dsinf(i*2*M_PI/(float)sectionsx);
      temp1.y = (sectionsy+(float)dsinf(j*(M_PI/2.0)/CAGE_DETAIL))*CAGE_HEIGHT;
      temp2.x = (float)dcosf((j+1)*(M_PI/2.0)/CAGE_DETAIL)*CAGE_RADIUS*(float)dcosf(i*2*M_PI/(float)sectionsx);
      temp2.z = (float)dcosf((j+1)*(M_PI/2.0)/CAGE_DETAIL)*CAGE_RADIUS*(float)dsinf(i*2*M_PI/(float)sectionsx);
      temp2.y = (sectionsy+(float)dsinf((j+1)*(M_PI/2.0)/CAGE_DETAIL))*CAGE_HEIGHT;
      Draw_Cylinder(&temp1,&temp2,CAGE_BAR_THICKNESS,CAGE_BAR_THICKNESS);
    }
  }
}

/* Draw s foot of the table */
SFUNCTION void Draw_Table_Foot(float x, float z)
{
  int i;
  point begin, end;
  float tableprofile[TABLE_DETAIL];
  for(i=0; i<TABLE_DETAIL; i++)
  {
    float t = (float)(2*M_PI*i/TABLE_DETAIL);
    tableprofile[i] = 0.4f+0.2f*dcosf(t) + 0.02f*dcosf(M_PI/2+t*5);
  }
  for(i=0; i<TABLE_DETAIL-1; i++)
  {
    begin.x = end.x = x;
    begin.z = end.z = z;
    begin.y = -TABLE_HEIGHT*(1-i/(float)TABLE_DETAIL);
    end.y = -TABLE_HEIGHT*(1-(i+1)/(float)TABLE_DETAIL);
    Draw_Cylinder(&begin, &end, TABLE_FOOT_RADIUS*tableprofile[i], TABLE_FOOT_RADIUS*tableprofile[i+1]);
  }
}

/* Draw the round table */
SFUNCTION void Draw_Table()
{
  int i;
  point begin, end;
  float tempy[BUFFER_SIZE];
  float tempr[BUFFER_SIZE];
  for(i=0; i<5; i++)
  {
    Draw_Table_Foot((TABLE_RADIUS + (TABLE_SIDE_RADIUS+TABLE_FLAT_PART_LEN)/2)*(float)dcosf(-0.2+i*2*M_PI/5), (TABLE_RADIUS + (TABLE_SIDE_RADIUS+TABLE_FLAT_PART_LEN)/2)*(float)dsinf(-0.2+i*2*M_PI/5));
  }
  for(i=0; i<=TABLE_DETAIL; i++)
  {
    float temporary = (float)(i*2*M_PI/TABLE_DETAIL);
    tempy[i] = -2.0f + TABLE_SIDE_RADIUS*(float)dsinf(temporary);
    tempr[i] = TABLE_RADIUS + TABLE_SIDE_RADIUS*(float)dcosf(temporary);
    if((i<=TABLE_DETAIL/4)||(i>3*TABLE_DETAIL/4))
    {
      tempr[i]+=TABLE_FLAT_PART_LEN;
    }
    if(i>0)
    {
      begin.x = end.x = 0.0;
      begin.z = end.z = 0.0;
      begin.y = tempy[i-1];
      end.y = tempy[i];
      Draw_Cylinder(&begin, &end, tempr[i-1], tempr[i]);
    }
  }
}

SCONST unsigned char section_amount_data[] =
{
  30, 0, 0,
  30, 0, 0,
  30, 0, 0,
  30, 0, 0,
  30, 0, 0,
  27, 0, 0,
  26, 0, 0,
  10, 10, 3,
  9, 9, 2,
  8, 8, 1,
  7, 7, 0,
  6, 6, 0,
  5, 5, 0,
  4, 4, 0,
  3, 3, 0,
  2, 2, 0,
  1, 1, 0
};

/*static const unsigned short int section_angle_data[] =
{
  6, 0, 0, 366, 0, 0,
  0, 0, 0, 360, 0, 0,
  6, 0, 0, 366, 0, 0,
  0, 0, 0, 360, 0, 0,
  6, 0, 0, 366, 0, 0,
  24, 0, 0, 348, 0, 0,
  30, 0, 0, 342, 0, 0,
  36, 216, 168, 156, 336, 204,
  42, 222, 174, 150, 330, 198,
  48, 228, 180, 144, 324, 192,
  54, 234, 0, 138, 318, 0,
  60, 240, 0, 132, 312, 0,
  66, 246, 0, 126, 306, 0,
  72, 252, 0, 120, 300, 0,
  78, 258, 0, 114, 294, 0,
  84, 264, 0, 108, 288, 0,
  90, 270, 0, 102, 282, 0
};*/

SCONST unsigned char section_angle_data[] =
{
     4,     0,     0,   244,     0,     0,
     0,     0,     0,   240,     0,     0,
     4,     0,     0,   244,     0,     0,
     0,     0,     0,   240,     0,     0,
     4,     0,     0,   244,     0,     0,
    16,     0,     0,   232,     0,     0,
    20,     0,     0,   228,     0,     0,
    24,   144,   112,   104,   224,   136,
    28,   148,   116,   100,   220,   132,
    32,   152,   120,    96,   216,   128,
    36,   156,     0,    92,   212,     0,
    40,   160,     0,    88,   208,     0,
    44,   164,     0,    84,   204,     0,
    48,   168,     0,    80,   200,     0,
    52,   172,     0,    76,   196,     0,
    56,   176,     0,    72,   192,     0,
    60,   180,     0,    68,   188,     0
};

/* Scene to screen */
SFUNCTION void Draw_Swan()
{
  int i,j;
  float base_r, base_h, head_r, head_h, flaps_r, flaps_h;
  // Draw the body
  for(i=0; i<17; i++)
  {
    
    if(i<8)
    {
      base_r = INITIAL_R+SWAN_RADIUS*(float)dsinf(M_PI*i/16.0);
      base_h = INITIAL_H+SWAN_RADIUS*(-(float)dcosf(M_PI*i/16.0)+1);
    }
    else
    {
      base_r = INITIAL_R+0.6f*SWAN_RADIUS + 0.4f*SWAN_RADIUS*(float)dsinf(M_PI/2 + M_PI*(i-8)/10.0);
      base_h = INITIAL_H+(((float)i-8.0f)/6.0f + 1.0f)*SWAN_RADIUS;
    }
    head_r = base_r + TRIANGLE_SIZE*(float)dsinf(i*M_PI*SWAN_LAYER_ANGLE_STEP/180);
    head_h = base_h - TRIANGLE_SIZE*(float)dcosf(i*M_PI*SWAN_LAYER_ANGLE_STEP/180);
    flaps_r = base_r + TRIANGLE_SIZE*(float)dcosf(i*M_PI*SWAN_LAYER_ANGLE_STEP/180);
    flaps_h = base_h + TRIANGLE_SIZE*(float)dsinf(i*M_PI*SWAN_LAYER_ANGLE_STEP/180);

    for(j=0; j<3; j++)
    {
      Draw_Flap_Circle(base_r, base_h, head_r, head_h, flaps_r, flaps_h, section_amount_data[i*3+j], section_angle_data[i*6+j], section_angle_data[i*6+j+3]);
    }
  }
  // Draw the neck
  Draw_Neck(0.9f*SWAN_RADIUS, 0.65f*SWAN_RADIUS, 0.0f);
}

//######################################
// GLSL ################################
//######################################

SCONST char shader_vars[] =
"varying vec3 a;"
"varying vec3 p;"
"varying vec4 c;"
"varying vec3 l;"
"varying vec3 n;"
"varying vec3 r;"
"varying vec3 v;"
"vec2 m(vec2 i1, vec2 i2){"
"return vec2(i1.x*i2.x-i1.y*i2.y,i1.x*i2.y+i1.y*i2.x);}"
"vec2 w(vec2 i1){"
"int i=0;"
"vec2 i2;"
"while(i<64){"
"i2=m(i1,i1);"
"i2=m(i2,i2);"
"i2=m(i2,i2);"
"i1=i2+i1;"
"if(length(i1)>4)return vec2(float(i),i/64.);"
"++i;}}"
"vec3 f(float s,float t){";

#if defined(USE_LD)
SCONST char shader_func_fragment_newton[] =
"vec2 i;"
"i.x=(s-500)/400;"
"i.y=(t-500)/400;"
"i=w(i)-w(i*1.2)+w(i*1.4)-w(i*1.6)+w(i*1.8);"
/*"c.r=c.g=0.4*i.y;"
"c.b=0.5*i.y;"
"c.a=i.y*16;"*/
"return vec3((s-500)*0.375,2*sqrt(i.x)/8.0-2.0,(t-500)*0.375);}"
"vec4 e(vec4 c){"
"return c*0.5";
#endif

SCONST char shader_funcs_newton[] =
"vec2 i;"
"i.x=(s-500)/400;"
"i.y=(t-500)/400;"
"i=w(i)-w(i*1.2)+w(i*1.4)-w(i*1.6)+w(i*1.8);"
"c.r=c.g=0.4*i.y;"
"c.b=0.5*i.y;"
"c.a=i.y*16;"
"return vec3((s-500)*0.375,2*sqrt(i.x)/8.0-2.0,(t-500)*0.375);}"
"vec4 e(vec4 c){"
"return c*0.5";
/*
static const char shader_funcs_table[] =
"c.r=0.9;"
"c.g=0.85;"
"c.b=0.8;"
"return vec3(s-790,-52.0+50*sin((1.0+clamp((abs(s-500.0)-450.0)/50.0,0.0,1.0)+clamp((abs(t-500.0)-450.0)/50.0,0.0,1.0))*3.1492/2.0),t*0.75-530)";
*/
SCONST char shader_funcs_cage[] =
"return a.xyz;}"
"vec4 e(vec4 c){"
"c.r=0.93;"
"c.g=0.91;"
"c.b=0.82;"
"return c";

SCONST char shader_funcs_glass[] =
"return a.xyz;}"
"vec4 e(vec4 c){"
"c.r=0.8;"
"c.g=0.8;"
"c.b=1.0;"
"c.a=0.15;"
"return c";

SCONST char shader_funcs_neutral[] =
"return a.xyz;}"
"vec4 e(vec4 c){"
"return c";

SCONST char shader_funcs_table[] =
"return a.xyz;}"
"vec4 e(vec4 c){"
"c.r=0.63;"
"c.g=0.60;"
"c.b=0.52;"
"return c";

SCONST char shader_front[] =
";}void main(){";

SCONST char shader_vertex[] =
"a=gl_Vertex.xyz;"
"n=gl_Normal;"
"c=gl_Color;"

"if(length(n)<=0){"
"r=f(a.x,a.y);"
"vec3 s=normalize(f(a.x+1.0,a.y)-r);"
"vec3 t=normalize(f(a.x,a.y+1.0)-r);"
"vec3 u=normalize(f(a.x-1.0,a.y)-r);"
"vec3 v=normalize(f(a.x,a.y-1.0)-r);"
"n=normalize(cross(s,v)+cross(v,u)+cross(u,t)+cross(t,s));}"
"else n=normalize(n);"

"a=r;"
"vec4 q=gl_ModelViewProjectionMatrix*vec4(a, 1.0);"
"gl_Position=q;"
"p=q.xyz;"

"l=vec3(75,250,20)-a.xyz;"
//"l=vec3(100.0,250.0,-50.0)-a.xyz;"
"v=vec3(300,300,200)-a.xyz;"
"r=normalize(l);"
"r=2.0*n*dot(r,n)-r;"

"gl_TexCoord[0]=gl_MultiTexCoord0;"
"}";

SCONST char shader_fragment[] =
"vec4 g=e(c);"
"g.xyz*=clamp(dot(normalize(l),normalize(n)),0.0,1.0);"
"float h=clamp(dot(normalize(v),normalize(r)),0.0,1.0);"
"g.xyz+=h*h*h*h;"
"gl_FragColor=g;"
"}";

static const char *shader_parts[] =
{
  shader_vars,
  NULL,
  shader_front,
  NULL
};

SFUNCTION unsigned shader_create(const char *sf, const char *ss, GLenum st)
{
  unsigned ret = dlCreateShader(st);

  shader_parts[1] = (char*)sf;
  shader_parts[3] = (char*)ss;

  dlShaderSource(ret, 4, (const GLchar**)shader_parts, NULL);
  dlCompileShader(ret);
#if defined(USE_LD)
  {
    GLint status;
    glGetShaderiv(ret, GL_COMPILE_STATUS, &status);
    if(status != GL_TRUE)
    {
      char slog[4096];
      GLsizei len;
      glGetShaderInfoLog(ret, 4096, &len, slog);
      puts(slog);
      exit(1);
    }
  }
  printf("GLSL shader with %p and %p: %u\n", sf, ss, ret);
#endif
  return ret;
}

#if defined(USE_LD)
SFUNCTION unsigned program_create(const char *vf)
#else
SFUNCTION void program_create(const char *vf)
#endif
{
  unsigned vi = shader_create(vf, shader_vertex, GL_VERTEX_SHADER);
  unsigned fi;
  unsigned ret;
#if defined(USE_LD)
  if(vf == shader_funcs_newton)
  {
    fi = shader_create(shader_func_fragment_newton, shader_fragment, GL_FRAGMENT_SHADER);
  }
  else
#endif
  {
    fi = shader_create(vf, shader_fragment, GL_FRAGMENT_SHADER);
  }

  ret = dlCreateProgram();
  dlAttachShader(ret, vi);
  dlAttachShader(ret, fi);
  dlLinkProgram(ret);
#if defined(USE_LD)
  {
    GLint status;
    glGetProgramiv(ret, GL_LINK_STATUS, &status);
    if(status != GL_TRUE)
    {
      char slog[4096];
      GLsizei len;
      glGetProgramInfoLog(ret, 4096, &len, slog);
      puts(slog);
      exit(1);
    }
  }
  printf("GLSL program with %p compiles to: %u\n", vf, ret);
  return ret;
#endif
}

#if defined(USE_LD)
unsigned program_newton;
unsigned program_cage;
unsigned program_glass;
unsigned program_neutral;
unsigned program_table;
#else
#define program_newton 3
#define program_cage 6
#define program_glass 9
#define program_neutral 12
#define program_table 15
#endif

//######################################
// Draw ################################
//######################################

SFUNCTION void map_grid(unsigned xsize, unsigned ysize)
{
  GLuint *iter = index_array;
  unsigned ii,jj;
  for(ii = 0; (ii < xsize); ++ii)
  {
    for(jj = 0; (jj < ysize); ++jj)
    {
      unsigned idx = ii * ysize + jj;
      interleaved_array *aa = draw_array + idx;

      aa->cr = aa->cg = aa->cb = aa->ca = 1.0f;
      aa->px = (float)ii;
      aa->py = (float)jj;
      aa->nx = aa->ny = aa->nz = 0.0f;
      aa->pz = 0.0f;

      if((ii < xsize - 1) && (jj < ysize - 1))
      {
        iter[0] = idx;
        iter[1] = idx + 1;
        iter[2] = idx + ysize + 1;
        iter[3] = idx + ysize;
        iter += 4;
      }
    }
  }
  dlDrawElements(GL_QUADS, (xsize - 1) * (ysize - 1) * 4, GL_UNSIGNED_INT, index_array);
}

SFUNCTION void draw()
{
  point begin, end;
  dlMatrixMode(GL_MODELVIEW);
  dlLoadIdentity();

  dluLookAt(300.0, 300.0, 200.0,
      0.0, 70.0, 0.0,
      0.0, 1.0, 0.0);

  /*glColor4f(1.0, 1.0, 1.0, 1.0);
  glBegin(GL_QUADS);
  glVertex3f(-100.0f, 0.0, -100.0f);
  glVertex3f(100.0f, 0.0, -100.0f);
  glVertex3f(100.0f, 0.0, 100.0f);
  glVertex3f(-100.0f, 0.0, 100.0f);
  glEnd();*/

  //glUseProgram(program_table);
  //map_grid(NEWTON_DETAIL, NEWTON_DETAIL);

  dlUseProgram(program_table);
  Draw_Table();
  dlDrawElements(GL_TRIANGLES, iiter, GL_UNSIGNED_INT, index_array);

  //Draw the goddamn glass
  dlUseProgram(program_glass);
  begin.x = begin.z = end.x = end.z = 0.0;
  begin.y = -2.01f;
  end.y = -2.00f;
  Draw_Cylinder(&begin, &end, TABLE_RADIUS, 0);
  dlDrawElements(GL_TRIANGLES, iiter, GL_UNSIGNED_INT, index_array);

  dlUseProgram(program_newton);
  map_grid(NEWTON_DETAIL, NEWTON_DETAIL);

  // Non-rectangular stuff
  dlUseProgram(program_neutral);
  Draw_Swan();
  dlDrawElements(GL_TRIANGLES, iiter, GL_UNSIGNED_INT, index_array);

  dlUseProgram(program_cage);
  Draw_Cage(20,2);
  dlDrawElements(GL_TRIANGLES, iiter, GL_UNSIGNED_INT, index_array);
}

//######################################
// Main ################################
//######################################

#if defined(__APPLE__)
#define MAINPROG SDL_main
#else
#define MAINPROG main
#endif

#if defined(USE_LD)
/** \brief GNU main function.
 *
 * @param argc Not used.
 * @param argv Not used.
 */
int MAINPROG(int argc_not_in_use, char *argv_not_in_use[])
{
#else
/** Required ELF symbol. */
void *environ;
/** Required ELF symbol. */
void *__progname;
/** \brief Object file starting point.
 */
void _start()
{
#endif

#if !defined(WIN32)
#if defined(USE_LD)
  // This is a replica of the loader to determine function addresses.
  {
    char *iter = (char*)dnload_symbols;
    do {
      for(;;)
      {
        void **olditer = (void**)iter;
        while(*(iter++));
        if(!*(iter))
        {
          break;
        }
        printf("dlfcn symbol offset %s: %i\n", iter, (int)(((char*)olditer) - dnload_symbols));
      }
    } while(*(++iter));
  }
#else
  // Load some symbols.
  {
    char *iter = (char*)dnload_symbols;
    do {
      void *handle = dlopen(iter, RTLD_LAZY);

      for(;;)
      {
        void **olditer = (void**)iter;

        while(*(iter++));
        if(!*(iter))
        {
          break;
        }

        *olditer = dlsym(handle, iter);      
      }
    } while(*(++iter));
  }
#endif
#endif

  //init_Graphics();
  DDL_Init(SDL_INIT_VIDEO);
  DDL_SetVideoMode(1280, 720, 32, SDL_OPENGL);
#if defined(USE_LD)
  {
    GLenum err = glewInit();
    if(err != GLEW_OK)
    {
      //problem: glewInit failed, something is seriously wrong
      //sprintf(ErrorMessage, "Error: %s\n", glewGetErrorString(err));
      return -1;
    }
  }
#endif
  
  {
    /* change to the projection matrix and set our viewing volume. */
    dlMatrixMode(GL_PROJECTION);
    dlLoadIdentity();
    dluPerspective(45.0, 1280.0 / 720.0, 1.0, 8192.0);
  }

  {
    dlEnable(GL_CULL_FACE);
    dlEnable(GL_DEPTH_TEST);
    dlEnable(GL_BLEND);
    dlBlendFunc(GL_SRC_ALPHA,  GL_ONE_MINUS_SRC_ALPHA);
    dlInterleavedArrays(GL_C4F_N3F_V3F, 0, draw_array);
  }

#if defined(USE_LD)
  program_newton = program_create(shader_funcs_newton);
  program_cage = program_create(shader_funcs_cage);
  program_glass = program_create(shader_funcs_glass);
  program_neutral = program_create(shader_funcs_neutral);
  program_table = program_create(shader_funcs_table);
#else
  program_create(shader_funcs_newton);
  program_create(shader_funcs_cage);
  program_create(shader_funcs_glass);
  program_create(shader_funcs_neutral);
  program_create(shader_funcs_table);
#endif

  /* wait for events */
  for(;;)
  {
    SDL_Event event;
    if(DDL_PollEvent(&event) && (event.type == SDL_KEYDOWN))
    {
#if defined(USE_LD)
      if(event.key.keysym.sym == SDLK_ESCAPE)
      {
#endif
        break;
#if defined(USE_LD)
      }
#endif
    }

    {
      static unsigned int drawn = 0;
      if(!drawn)
      {
        dlClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        draw();
        drawn = 1;
        DDL_GL_SwapBuffers(); /* Only flip once. */
      }
    }

#if defined(USE_LD)
    SDL_Delay(10);
#endif
  }

  DDL_Quit();
#if !defined(USE_LD)
  asm("movl $1,%eax\nxor %ebx,%ebx\nint $128\n");
#else
  return 0;
#endif
}

//######################################
// End #################################
//######################################

